package org.skycloud.mybatis;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.skycloud.mybatis.dialect.DefaultDialectFactory;
import org.skycloud.mybatis.dialect.DialectFactory;
import org.skycloud.mybatis.filter.Filter;
import org.skycloud.mybatis.filter.wrapper.FilterObjectWrapperFactory;

public final class SqlSessionFactoryBean extends org.mybatis.spring.SqlSessionFactoryBean {

	private DataSource dataSource;
	private Interceptor[] interceptors;
	private DialectFactory dialectFactory;
	private Map<String, String> vendorMapping;

	@Override
	public void setDataSource(DataSource dataSource) {
		super.setDataSource(dataSource);
		this.dataSource = dataSource;
	}

	public void setInterceptors(Interceptor... interceptors) {
		this.interceptors = interceptors;
	}

	public void setDialectFactory(DialectFactory dialectFactory) {
		this.dialectFactory = dialectFactory;
	}

	public void setVendorMapping(Map<String, String> vendorMapping) {
		this.vendorMapping = vendorMapping;
	}

	@Override
	protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
		if (getDatabaseIdProvider() == null
				|| VendorDatabaseIdProvider.class.equals(getDatabaseIdProvider().getClass())) {
			setDatabaseIdProvider(new DatabaseIdProviderImpl(vendorMapping));
		}

		try {
			Field field = org.mybatis.spring.SqlSessionFactoryBean.class.getDeclaredField("typeAliases");
			field.setAccessible(true); // Play dirty
			Class<?>[] typeAliases = (Class<?>[]) field.get(this);

			boolean filterRegistered = false;
			for (Class<?> typeAlias : typeAliases) {
				if (Filter.class.equals(typeAlias)) {
					filterRegistered = true;
					break;
				}
			}

			// Register our filter if not registered
			if (!filterRegistered) {
				Class<?>[] newArray = Arrays.copyOf(typeAliases, typeAliases.length + 1);
				newArray[newArray.length - 1] = Filter.class;

				setTypeAliases(newArray);
			}
		} catch (NoSuchFieldException e) {
			throw new IllegalStateException("There's no [typeAliases] field of type ["
					+ org.mybatis.spring.SqlSessionFactoryBean.class.getName() + "].", e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException("Can't read typeAliases field value.", e);
		}

		SqlSessionFactory sqlSessionFactory = super.buildSqlSessionFactory();

		Configuration configuration = sqlSessionFactory.getConfiguration();
		configuration.setMapUnderscoreToCamelCase(true);

		ObjectWrapperFactory objectWrapperFactory = configuration.getObjectWrapperFactory();
		if (objectWrapperFactory == null || DefaultObjectWrapperFactory.class.equals(objectWrapperFactory.getClass())) {
			configuration.setObjectWrapperFactory(new FilterObjectWrapperFactory());
		}

		if (dialectFactory == null) {
			dialectFactory = new DefaultDialectFactory(dataSource);
		}
		configuration.addInterceptor(new RowBoundsInterceptor(dialectFactory.getDialect()));

		if (interceptors != null) {
			for (Interceptor interceptor : interceptors) {
				configuration.addInterceptor(interceptor);
			}
		}

		return sqlSessionFactory;
	}
}
